home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 7
/
Apprentice-Release7.iso
/
Environments
/
Clean 1.2.4
/
IO Examples
/
Simple DataBase
/
database.icl
< prev
next >
Wrap
Text File
|
1997-05-16
|
19KB
|
438 lines
module database // Small database program to manipulate a simple database
import StdEnv
import deltaEventIO, deltaDialog, deltaIOSystem, deltaMenu, deltaWindow, deltaFont
import deltaPicture, deltaIOState, deltaFileSelect, deltaControls, deltaSystem
import listextensions
:: *IO :== IOState DataBase // Synonym for IOState (see deltaEventIO)
:: *DataBase :== (State, Files) // State contains all relevant info
:: Record :== [ String ] // [Content]
:: Descriptor :== [ String ] // [Fieldname]
:: State = { records :: [Record] // All records
, descriptor :: Descriptor // All fieldnames
, selection :: Int // Indicating current record selected
, query :: Record // Record to look for
, name :: String // Name of database
, editinfoid :: DialogItemId // Id of info about use of editdialog (query or record)
, fw :: Int // Max width of field contents
, dw :: Int // Max width of descriptor fields
}
:: InfoFont = { font :: Font // The font which is used
, width :: Int // Its widest character
, height :: Int // Its line height
}
MinDbDomainSize :== (100,1) // Minimal size of recordwindow
CharsInInputBox :== 20 // Input width (number of characters)
InputBoxWidth :== Pixel (CharsInInputBox*DfFont.width)// Width of boxes in fields, queries and field names
DontCareId :== 0
RecordWindowId :== 0 // Id of window in which the records are shown
EdDialogId :== 0; FieldDialogId :== 1 // Ids of main dialogs used
Replace :== True // Replace current selection when adding new record
Separator :== ": " // Separates field names and contents
DbFont =: {font = f, width = maxwidth, height = ascent+descent+leading}
where // Global graph def: font used in this database
(ascent,descent,maxwidth,leading) = FontMetrics f
(_,f) = SelectFont "courier" [] 10
DfFont =: {font = f, width = maxwidth, height = ascent+descent+leading}
where // Global graph def: default font (in dialogs)
(ascent,descent,maxwidth,leading) = FontMetrics f
(_,f) = SelectFont name styles length
(name,styles,length) = DefaultFont
Start :: *World -> *World
Start world
# (events,world) = OpenEvents world
(files, world) = openfiles world
((_,finalfiles),finalevents)
= StartIO [MenuSystem [menu]] (initState,files) initIO events
world = CloseEvents finalevents world
world = closefiles finalfiles world
= world
where
menu = PullDownMenu DontCareId "Commands" Able
[ MenuItem DontCareId "Show Records" (Key 'r') Able ShowRecords
, MenuItem DontCareId "Edit..." (Key 'e') Able ShowEditDialog
, MenuItem DontCareId "Change Set Up..." (Key 'u') Able ShowFieldDialog
, MenuItem DontCareId "Read new..." (Key 'o') Able (\s io->seqIO initIO (s, seq closeIO io))
, MenuItem DontCareId "Save As..." (Key 's') Able SaveRecords
, MenuSeparator
, MenuItem DontCareId "Quit" (Key 'q') Able Quit
]
initIO = [ ReadDataBase, ShowRecords, ShowEditDialog ]
closeIO = [ CloseWindows [RecordWindowId], closeDbDialogs ]
initState = { records=[],descriptor=[],selection=0,query=[],name="",editinfoid=0,fw=0,dw=0 }
// The CallBack and initialisation Functions of the menu:
ReadDataBase :: DataBase IO -> (DataBase, IO)
ReadDataBase db io
# (done,dbname,(state, files),io) = SelectInputFile db io
| not done = ((state,files),io)
# (open,dbfile,files) = fopen dbname FReadText files
| not open = ((state,files),Beep io)
# (descr,dbfile) = FReadDescr dbfile
(recs, dbfile) = FReadRecords (inc (length descr)) dbfile // lines = length descr + empty line
(close,files) = fclose dbfile files
| not close = ((state,files),Beep io)
| otherwise = (({state & records=recs,descriptor=descr,query=repeatn (length descr) "",selection=0,name=dbname,
fw=MaxWidth DbFont.font (flatten recs),dw=MaxWidth DbFont.font descr},files)
,io
)
where
FReadDescr file
# (nroffields,file) = FReadStrippedLine file
(descr,file) = seqList (repeatn (toInt nroffields) FReadStrippedLine) file
= (descr,file)
FReadRecords nroflines file
| sfend file = ([], file)
# ([_:record],file) = seqList (repeatn nroflines FReadStrippedLine) file
(records, file) = FReadRecords nroflines file
= ([record : records], file)
FReadStrippedLine file
# (line, file) = freadline file
= (line%(0,size line - 2),file) // strip "\n"
ShowRecords :: DataBase IO -> (DataBase, IO)
ShowRecords (state=:{records,descriptor,dw,name}, files) io
= ((state,files),OpenWindows [window] io)
where
window = ScrollWindow RecordWindowId (5,5) namewithoutdirectories
(ScrollBar (Thumb left) (Scroll DbFont.width)) (ScrollBar (Thumb top) (Scroll DbFont.height))
domain MinDbDomainSize (right - left,bottom - top)
UpdateRecordWindow [Mouse Able MouseSelectItem]
namewithoutdirectories = toString (last (splitby DirSeparator (fromString name)))
((left,top),(right,bottom)) = domain
domain = DbPictureDomain state 0 (max (length records) 1)
ShowEditDialog :: DataBase IO -> (DataBase, IO)
ShowEditDialog (state=:{descriptor=descr,records=recs,selection},files) io
# io = OpenDialog editDialog io
io = SetTextFields infoid infostring descr (if (isEmpty recs) [] (recs!!selection)) io
= (({state & editinfoid = infoid},files), io)
where
infostring = "Current Record Number: "+++toString selection
editDialog = CommandDialog EdDialogId "Edit Record" [] addId dialogitems
dialogitems = [ DynamicText infoid Left InputBoxWidth "" ]
++ flatten [inputfield sid eid field \\ field <- descr & eid <- [0..] & sid <- [length descr..]]
++
[ DialogButton dispQId (Below (length descr - 1)) "DisplQ" Able DisplQuery
, DialogButton setQId (RightTo dispQId) "SetQ" Able SetQuery
, DialogButton srchQId (RightTo setQId) "SearchQ" Able Search
, DialogButton slctQId (RightTo srchQId) "SelectAllQ" Able SelectAll
, DialogButton replId (Below dispQId) "Replace" Able (AddRecord Replace)
, DialogButton delId (RightTo replId) "Delete" Able DeleteRecord
, DialogButton addId (RightTo delId) "Add" Able (AddRecord (not Replace))
, DialogButton sortId (RightTo addId) "Sort" Able Sort
]
inputfield sid eid field
= [StaticText sid Left field, EditText eid pos InputBoxWidth 1 ""]
where
pos = case eid of 0 -> XOffset sid offset; else -> Below (dec eid)
offset = Pixel (DfFont.width + MaxWidth DfFont.font descr - MaxWidth DfFont.font [field])
[infoid,dispQId,setQId,srchQId,slctQId,replId,delId,addId,sortId:_] = [2*(length descr)..]
ShowFieldDialog :: DataBase IO -> (DataBase, IO)
ShowFieldDialog db=:({descriptor=d},_) io
| isEmpty d = inputdialog "Give first field" InputBoxWidth (\input->FieldChangeIO (add (-1) input)) db io
| otherwise = (db,OpenDialog fielddialog (CloseDialog EdDialogId io))
with
fielddialog = CommandDialog FieldDialogId "Change Set Up" [] addId
[StaticText DontCareId Left "Select Field...",
RadioButtons selectId Left (Columns 1) firstRadioId (radioitems firstRadioId d),
DialogButton deleteId Left "Delete" Able (DeleteField getselectedfield),
DialogButton moveId (RightTo deleteId) "Move" Able (MoveField getselectedfield),
DialogButton renameId Left "Rename" Able (RenameField getselectedfield),
DialogButton addId (Below moveId) "Add New" Able (AddField getselectedfield)]
getselectedfield dialoginfo = GetSelectedRadioItemId selectId dialoginfo - firstRadioId
[deleteId,moveId,renameId,addId,selectId,firstRadioId:_] = [0..]
SaveRecords :: DataBase IO -> (DataBase, IO)
SaveRecords db=:({name,descriptor,records},_) io
# (done,dbname,db,io) = SelectOutputFile "Save As: " name db io
| not done = (db, io)
# (state,files) = db
(open,dbfile,files) = fopen dbname FWriteText files
| not open = ((state,files), Beep io)
# (close,files) = fclose (seq (writedescriptor++writerecords) dbfile) files
| not close = ((state, files), Beep io)
| otherwise = ((state, files), io)
where
writedescriptor = [fwritei (length descriptor), FWriteRecord descriptor]
writerecords = [FWriteRecord rec \\ rec <- records]
FWriteRecord rec = fwrites (foldl (+++) "\n" (map (\field -> field +++ "\n") rec))
Quit :: DataBase IO -> (DataBase, IO)
Quit database io = (database, QuitIO io)
// Field set up changes
FieldChangeIO :: (State -> State) DataBase IO -> (DataBase,IO)
FieldChangeIO changefun (state,files) io = UpdateDbDomain (changefun state,files) (closeDbDialogs io)
AddField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
AddField getfield dialoginfo db=:(state,files) io
= inputdialog infotext InputBoxWidth (\input->FieldChangeIO (add fieldname input)) db io
where
infotext = "Add after '"+++state.descriptor!!fieldname+++"' new field"
fieldname = getfield dialoginfo
RenameField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
RenameField getfield dialoginfo db=:(state,files) io
= inputdialog infotext InputBoxWidth (\input->FieldChangeIO (rename fieldtorename input)) db io
where
infotext = "Rename '"+++state.descriptor!!fieldtorename+++"' to"
fieldtorename = getfield dialoginfo
MoveField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
MoveField getfield dialoginfo db=:({descriptor=d},_) io
= (db,OpenDialog movedialog io)
where
fieldtomove = getfield dialoginfo
movedialog
= CommandDialog moveDialogId "Move Field" [] okId
[ StaticText infoId Left ("Move '"+++(d!!fieldtomove)+++ "' before: ")
, RadioButtons selectId Left (Rows (inc (length d))) firstRadioId (radioitems firstRadioId (d++[""]))
, DialogButton cancelId Left Cancel Able cancel
, DialogButton okId (RightTo cancelId) "Move" Able (ok (move fieldtomove))
]
[moveDialogId,cancelId,okId,infoId, selectId,firstRadioId:_] = [0..]
ok mvf dlginfo s io
= FieldChangeIO (mvf destinationfield) s (CloseDialog moveDialogId io)
where
destinationfield = GetSelectedRadioItemId selectId dlginfo - firstRadioId
DeleteField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
DeleteField getfield dialoginfo db io
= warn ["Are you sure?"] (FieldChangeIO (delete (getfield dialoginfo))) db io
add afterfield fieldname state=:{records=rs,descriptor=d,query=q,dw}
= {state & records=map (ins "") rs,descriptor=ins fieldname d,query=ins "" q,dw=descrwidth}
where
ins x ys = insertAt (inc afterfield) x ys
descrwidth = max (MaxWidth DbFont.font [fieldname]) dw
rename selectedfield newfieldname s=:{descriptor=d}
= {s & descriptor=newdescr,dw=MaxWidth DbFont.font newdescr}
where
newdescr = updateAt selectedfield newfieldname d
move sf df s=:{records=rs,descriptor=d,query=q}
= {s & records=map (moveinlist sf df) rs,descriptor=moveinlist sf df d,query=moveinlist sf df q}
delete i s=:{records=rs,descriptor=d,query=q}
= {s & records=newrs,descriptor=newdescr,query=remove i q,dw=MaxWidth DbFont.font newdescr,fw=nfw}
where
newrs = map (remove i) rs
newdescr = remove i d
nfw = MaxWidth DbFont.font (flatten newrs)
// Handling the edit dialog
DisplQuery ::DialogInfo DataBase IO -> (DataBase, IO)
DisplQuery info db=:({descriptor,query,editinfoid},_) io
= (db,SetTextFields editinfoid "Query :" descriptor query io)
SetQuery ::DialogInfo DataBase IO -> (DataBase, IO)
SetQuery info (state, files) io
# (nquery,io) = GetTextFields state.descriptor io
= (({state & query = nquery},files), io)
Search ::DialogInfo DataBase IO -> (DataBase, IO)
Search info database=:(state=:{records,query,selection=sel},files) io
| isEmpty found = (database, Beep io)
| otherwise = MakeSelectionVisible ({state & selection=nsel},files) (ChangeSelection state sel nsel io)
where
nsel = hd found
found = [i \\ e <- el ++ bl & i <- [sel+1 .. length records - 1] ++ [0..] | QueryRecord query e]
(bl,el) = splitAt (sel+1) records
QueryRecord :: Record Record -> Bool
QueryRecord query e
= and [ EqPref qf f \\ f <- e & qf <- query ]
where
EqPref pref name
| size pref > size name = False
| otherwise = pref == name%(0,size pref - 1)
SelectAll :: DialogInfo DataBase IO -> (DataBase, IO)
SelectAll info database=:(state=:{records,query,selection,descriptor},files) io
| isEmpty recs = (database, Beep io)
# io = ChangeSelection state selection 0 io
io = ChangeWindowTitle RecordWindowId selname io
| otherwise = UpdateDbDomain (nstate,files) io
where
recs = filter (QueryRecord query) records
nstate = {state & selection=0,records=recs,name=selname,fw=MaxWidth DbFont.font (flatten recs)}
selname = "Select"
MakeSelectionVisible :: DataBase IO -> (DataBase,IO)
MakeSelectionVisible db=:({records,selection,descriptor},_) io
| isEmpty records = (db,io)
| selection_invisible = ChangeScrollBar RecordWindowId (ChangeVThumb selthumb) db io1
| otherwise = (db,io1)
where
(((_,visibletop),(_,visiblebot)), io1)
= WindowGetFrame RecordWindowId io
selection_invisible = selthumb < visibletop || selthumb >= visiblebot
selthumb = toPicCo descriptor selection
DeleteRecord :: DialogInfo DataBase IO -> (DataBase, IO)
DeleteRecord dialogInfo db=:(state=:{records=oldrecs,selection=index,descriptor,fw},files) io
| isEmpty oldrecs = (db,Beep io)
| otherwise = UpdateDbDomain (nstate,files) io
where
newrecs = remove index oldrecs
fieldwidth = if recalcwidth (MaxWidth DbFont.font (flatten newrecs)) fw
recalcwidth = fw == MaxWidth DbFont.font (oldrecs!!index)
nindex = if (isEmpty newrecs) 0 (index mod length newrecs)
nstate = {state & records = newrecs, selection = nindex, fw = fieldwidth}
AddRecord :: Bool DialogInfo DataBase IO -> (DataBase, IO)
AddRecord replace dialogInfo db=:(state=:{descriptor,selection,records=recs,fw},files) io
| isEmpty recs && replace = (db,Beep io)
| otherwise = UpdateDbDomain (nstate,files) io1
where
(newrec,io1) = GetTextFields descriptor io
(index,newrecs) = insertindex (\a b -> a <= b) newrec (if replace (remove selection recs) recs)
fieldwidth = if recalc (MaxWidth DbFont.font (flatten newrecs)) (max (MaxWidth DbFont.font newrec) fw)
recalc = replace && MaxWidth DbFont.font (recs!!selection) < fw
nstate = {state & records=newrecs,selection=index,fw=fieldwidth}
Sort :: DialogInfo DataBase IO -> (DataBase, IO)
Sort dialogInfo (state=:{records=recs},files) io
= UpdateDbDomain ({state & records = sort recs},files) io
GetTextFields :: Descriptor IO -> (Record,IO)
GetTextFields descr io
= ([GetEditText id dialogInfo \\ id <- [0..(length descr - 1)]],nio)
where
(_,dialogInfo,nio) = GetDialogInfo EdDialogId io
SetTextFields :: Int String Descriptor Record IO ->IO
SetTextFields infoid s d rec io
= ChangeDialog EdDialogId dialogchanges io
where
dialogchanges = [ChangeDynamicText infoid s : [ChangeEditText id f \\ id <- [0.. length d - 1] & f <- rec]]
// Handling mouse clicks in database window
MouseSelectItem :: MouseState DataBase IO -> (DataBase, IO)
MouseSelectItem ((_,mvpos), ButtonDown, _) (state=:{records,descriptor,selection}, files) io
| isEmpty records = ((state, files), io)
| otherwise = (({state & selection=index},files),ChangeSelection state selection index io)
where
index = toRecCo descriptor mvpos
MouseSelectItem _ database io
= (database, io)
// Drawing utilities
DbPictureDomain :: State Int Int -> PictureDomain
DbPictureDomain state=:{descriptor=d,records,dw,fw} fr to
| (right-left,bottom-top) < MinDbDomainSize
= ((~whiteMargin, 0),(~whiteMargin+width,height))
| otherwise = ((left ,top),( right,bottom))
where
(width,height) = MinDbDomainSize
whiteMargin = DbFont.width
((left,top),(right,bottom)) = ( (~whiteMargin ,toPicCo d fr)
, (dw + MaxWidth DbFont.font [Separator] + fw + whiteMargin,toPicCo d to)
)
UpdateDbDomain :: DataBase IO -> (DataBase,IO)
UpdateDbDomain db=:(state,files) io
# (db,io) = ChangePictureDomain RecordWindowId (DbPictureDomain state 0 (max (length state.records) 1)) db io
(db,io) = DrawInWindowFrame RecordWindowId UpdateRecordWindow db io
(db,io) = MakeSelectionVisible db io
= (db,io)
UpdateRecordWindow :: UpdateArea DataBase -> (DataBase, [DrawFunction])
UpdateRecordWindow domains db=:(state=:{records=recs,descriptor=descr,selection}, _)
= (db,[SetFont DbFont.font : flatten (map Update domains)] ++ HiliteSelection state selection)
where
Update domain=:((_,top),(_,bottom))
| isEmpty recs = [EraseRectangle domain]
| otherwise = [EraseRectangle domain, MovePenTo (0,topofvisiblerecs) : map (DrawRec descr) (recs%(toprec,botrec))]
where
topofvisiblerecs= toPicCo descr toprec
toprec = toRecCo descr top
botrec = toRecCo descr (dec bottom)
DrawRec descr rec
= seq (drawLine "" ++ flatten [drawLine (d +++ Separator +++ f) \\ d<-normwidth descr & f<-rec])
where
normwidth descr = [f +++ toString (spaces ((maxList (map (size ) descr)) - size f)) \\ f <- descr]
drawLine s = [DrawString s,MovePen (~(FontStringWidth s DbFont.font),DbFont.height)]
ChangeSelection:: State Int Int IO -> IO
ChangeSelection state=:{descriptor=descr,records,editinfoid} old new io
# io = DrawInWindow RecordWindowId (HiliteSelection state old ++ HiliteSelection state new) io
io = SetTextFields editinfoid infostring descr (records!!new) io
= io
where
infostring = "Current Rec Nr: "+++toString new
HiliteSelection :: State Int -> [Picture -> Picture]
HiliteSelection s i
= [ SetPenMode HiliteMode, FillRectangle (DbPictureDomain s i (inc i)), SetPenNormal, SetPenColour BlackColour ]
// Switching between picture coordinates and indices in the list of records ('record coordinates')
toPicCo:: Descriptor Int -> Int
toPicCo descr n = n * (inc (length descr) * DbFont.height)
toRecCo:: Descriptor Int -> Int
toRecCo descr n = n / (inc (length descr) * DbFont.height)
// Various useful functions
closeDbDialogs io = seq (map CloseDialog [FieldDialogId,EdDialogId]) io
radioitems firstid titles = [RadioItem id t Able (\ _ x -> x) \\ id <- [firstid..] & t <- titles]
MaxWidth font [] = 0
MaxWidth font list = maxList (FontStringWidths list font)
// functions that should be library functions
seqIO fs = seq (map uncurry fs) // should be in deltaEventIO, will be obsolete with new IO-library
Cancel :== "Cancel"
OK :== "OK"
inputdialog name width fun s io
= (s,OpenDialog dialogdef io)
where
dialogdef = CommandDialog dlgId name [] okId
[ StaticText nameId Left (name+++": "),EditText inputId (RightTo nameId) width 1 ""
, DialogButton cancelId (Below inputId) Cancel Able cancel
, DialogButton okId (RightTo cancelId) OK Able (ok fun)
]
ok fun dlginfo s io = fun (GetEditText inputId dlginfo) s (CloseDialog dlgId io)
[dlgId,nameId,inputId,cancelId,okId:_] = [0..]
warn info fun s io
# (choiceId,s,io) = OpenNotice warningdef s io
| choiceId == cancelId = (s,io)
| otherwise = fun s io
where
warningdef = Notice info (NoticeButton cancelId Cancel) [NoticeButton okId OK]
cancelId = 0
okId = 1
cancel _ s io = (s, CloseActiveDialog io)